// 版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

package modfetch

import (
	"fmt"
	"io"
	"io/fs"
	"os"
	"strconv"
	"time"

	"cmd/go/internal/cfg"
	"cmd/go/internal/modfetch/codehost"
	"cmd/go/internal/par"
	"cmd/go/internal/vcs"
	web "cmd/go/internal/web"

	"golang.org/x/mod/module"
)

const traceRepo = false // 跟踪所有回购操作，以进行调试

// Repo表示存储单个模块的所有版本的存储库。
// 多个goroutine同时使用它必须是安全的。
type Repo interface {
	// ModulePath返回模块路径。
	ModulePath() string

	// 版本列出具有给定前缀的所有已知版本。
	// 不包括伪版本。
	// None
	// 返回的版本应按版本顺序排序
	// （实现可以使用semver.Sort）。
	// None
	// 只有在出现问题时，版本才会返回非nil错误
	// 获取版本列表：它可能返回一个空列表
	// 如果匹配版本列表
	// 已知是空的。
	// None
	// 如果基础存储库不存在，
	// Versions返回一个与errors.Is（\uOS.NotExist）匹配的错误。
	Versions(prefix string) ([]string, error)

	// Stat返回有关修订版本的信息。
	// 修订可以是基础服务已知的任何标识符：
	// 提交散列、分支、标记等。
	Stat(rev string) (*RevInfo, error)

	// Latest返回默认分支上的最新版本，
	// 无论这在底层源代码存储库中意味着什么。
	// 它仅在没有标记版本时使用。
	Latest() (*RevInfo, error)

	// GoMod返回给定版本的go.mod文件。
	GoMod(version string) (data []byte, err error)

	// Zip将给定版本的Zip文件写入dst。
	Zip(dst io.Writer, version string) error
}

// 版本描述模块存储库中的单个版本。
type RevInfo struct {
	Version string    // 此修订版的建议版本字符串
	Time    time.Time // 承诺时间

	// 这些字段用于统计任意版本，
	// 但是，在谈论模块版本时，它们不会被记录下来。
	Name  string `json:"-"` // 基础存储库中的完整ID
	Short string `json:"-"` // 缩短的ID，用于伪版本
}

// Re：模块路径、导入路径、存储库根和查找
// None
// 模块是存储在文件树中的Go包的集合
// 在树的根目录下使用go.mod文件。
// mod定义模块路径，即导入路径
// 对应于文件树的根。
// 该文件树中目录的导入路径是模块路径
// 与相对于根的子目录的名称联接。
// None
// 例如，路径为rsc.io/qr的模块对应于
// 存储库中的文件树https:
// 该文件树有一个go.mod，上面写着“module rsc.io/qr”。
// 根目录中的包具有导入路径“rsc.io/qr”。
// gf256子目录中的包具有导入路径“rsc.io/qr/gf256”。
// 在本例中，“rsc.io/qr”既是模块路径又是导入路径。
// 但“rsc.io/qr/gf256”只是一个导入路径，不是模块路径：
// 它命名一个可导入的包，但不是一个模块。
// None
// 作为一种特殊情况，合并在创建模块之前编写的代码
// 引入，如果路径p使用前模块“go-get”查找解析
// 到源代码存储库的根目录，而不使用go.mod文件，
// 该存储库被视为根目录中有一个go.mod
// 声明模块路径p。（go.mod进一步被认为是
// 包含与任何旧版本对应的需求
// 跟踪格式，如Gopkg.lock、vendor/vendor.conf等。）
// None
// 到目前为止，该演示文稿忽略了一个事实，即源代码存储库
// 具有文件树的许多不同版本，并且这些版本可能
// 不同于是否存在特定的go.mod以及它包含什么。
// 事实上，只有从模块路径，版本，定义良好的映射
// 结对-经常写path@version-到特定的文件树。
// 例如rsc.io/qr@v0.1.0取决于根目录下的“隐式go.mod”
// 存储库”规则，而rsc.io/qr@v0.2.0有一个显式的go.mod。
// 因为“go-get”导入路径为rsc.io/qr和github.com/rsc/qr
// 两者都重定向到Git存储库https:
// github.com/rsc/qr@v0.1.0与rsc.io的文件树相同/qr@v0.1.0
// 但是不同的模块（不同的名称）。相比之下，自v0.2.0起
// 该存储库的一部分有一个显式的go.mod，它声明路径rsc.io/qr，
// github.com/rsc/qr@v0.2.0是无效的模块路径、版本对。
// 在使用模块之前，导入注释也会产生相同的效果。
// None
// 与给定模块路径关联的导入路径集为
// 显然不是固定的：至少，新目录具有新的导入路径
// 可以随时添加。但另一个潜在的操作是拆分
// 子树从一个模块转换到它自己的模块中。如果仔细做，
// 可以在保留客户端兼容性的同时执行此操作。
// 例如，假设我们要将rsc.io/qr/gf256拆分为
// 自己的模块，因此将有两个模块rsc.io/qr和rsc.io/qr/gf256。
// 然后我们可以同时发布rsc.io/qr v0.3.0（删除gf256子目录）
// 和rsc.io/qr/gf256 v0.1.0，包括在各自的go.mod中
// 相互指向的循环要求：rsc.io/qr v0.3.0要求
// rsc.io/qr/gf256 v0.1.0，反之亦然。然后可以创建一个构建
// 使用包含gf256软件包的旧rsc.io/qr模块，但如果
// 它在较新的rsc.io/qr或较新的rsc.io/qr上添加了一个要求
// rsc.io/qr/gf256模块，自动添加需求
// 在互补的一半上，确保rsc.io/qr/gf256
// 可由生成导入，并且仅定义了它
// 通过单个模块。gf256软件包可以移回
// rsc.io/qr v0.4.0的另一个同步版本的原件，包括
// gf256子目录和一个rsc.io/qr/gf256 v0.2.0（不带代码）
// 在其根目录中，以及新的需求周期。
// 以这种方式改变模块边界的能力预计将
// 在大规模程序重构中非常重要，类似于
// 在https中描述：
// None
// 改变模块边界的可能性再次强调
// 您必须知道模块路径及其版本
// 确定该模块直接提供的包集。
// None
// 除此之外，还可以使用单个代码存储库
// 要在分支或子目录中包含多个模块，
// 作为一种有限的单一回购。例如rsc.io/qr/v2，
// rsc.io/qr的v2.x.x续集有望找到
// 在v2中，https中的标记提交：
// 在根目录或v2子目录中，通过go.mod消除歧义。
// 同样，与模块对应的精确文件树
// 取决于我们正在考虑的版本。
// None
// 基础存储库也可能随时间而变化，
// 无需更改模块路径。如果我复制github回购协议
// 至https:
// 然后，所有版本的客户端都应该开始从bitbucket获取数据
// 而不是github。也就是说，与确切的文件树不同，
// 与模块路径关联的源代码存储库的位置
// 不取决于模块版本。（总体而言，这是出于设计
// 这些重定向的目的是允许包作者建立一个稳定的
// 可以在代码从一个服务移动到另一个服务时更新的名称。）
// None
// 所有这些都是本文中定义的查找API的重要背景
// 文件
// None
// Lookup函数采用模块路径并返回表示
// 那个模块路径。仅对路径进行查找只能起到很小的作用。
// 它可以检查路径是否格式正确（请参阅semver.CheckPath）
// 它可以检查路径是否可以解析为目标存储库。
// 为了避免版本控制访问，除非绝对必要，
// 查找不会尝试连接到存储库本身。

var lookupCache par.Cache

type lookupCacheKey struct {
	proxy, path string
}

// 查找返回具有给定模块路径的模块，
// 通过给定的代理获取。
// None
// 可分辨代理“direct”表示应该获取路径
// “noproxy”表示应该获取补丁
// 仅当GONOPROXY与给定路径匹配时才直接使用。
// None
// 对于可分辨代理“off”，查找始终返回返回
// 每个方法调用的非nil错误。
// None
// 成功返回并不保证模块
// 有任何定义的版本。
func Lookup(proxy, path string) Repo {
	if traceRepo {
		defer logCall("Lookup(%q, %q)", proxy, path)()
	}

	type cached struct {
		r Repo
	}
	c := lookupCache.Do(lookupCacheKey{proxy, path}, func() interface{} {
		r := newCachingRepo(path, func() (Repo, error) {
			r, err := lookup(proxy, path)
			if err == nil && traceRepo {
				r = newLoggingRepo(r)
			}
			return r, err
		})
		return cached{r}
	}).(cached)

	return c.r
}

// 查找返回具有给定模块路径的模块。
func lookup(proxy, path string) (r Repo, err error) {
	if cfg.BuildMod == "vendor" {
		return nil, errLookupDisabled
	}

	if module.MatchPrefixPatterns(cfg.GONOPROXY, path) {
		switch proxy {
		case "noproxy", "direct":
			return lookupDirect(path)
		default:
			return nil, errNoproxy
		}
	}

	switch proxy {
	case "off":
		return errRepo{path, errProxyOff}, nil
	case "direct":
		return lookupDirect(path)
	case "noproxy":
		return nil, errUseProxy
	default:
		return newProxyRepo(proxy, path)
	}
}

type lookupDisabledError struct{}

func (lookupDisabledError) Error() string {
	if cfg.BuildModReason == "" {
		return fmt.Sprintf("module lookup disabled by -mod=%s", cfg.BuildMod)
	}
	return fmt.Sprintf("module lookup disabled by -mod=%s\n\t(%s)", cfg.BuildMod, cfg.BuildModReason)
}

var errLookupDisabled error = lookupDisabledError{}

var (
	errProxyOff       = notExistErrorf("module lookup disabled by GOPROXY=off")
	errNoproxy  error = notExistErrorf("disabled by GOPRIVATE/GONOPROXY")
	errUseProxy error = notExistErrorf("path does not match GOPRIVATE/GONOPROXY")
)

func lookupDirect(path string) (Repo, error) {
	security := web.SecureOnly

	if module.MatchPrefixPatterns(cfg.GOINSECURE, path) {
		security = web.Insecure
	}
	rr, err := vcs.RepoRootForImportPath(path, vcs.PreferMod, security)
	if err != nil {
		// 我们不知道在哪里可以找到具有此路径的模块的代码。
		return nil, notExistError{err: err}
	}

	if rr.VCS.Name == "mod" {
		// 使用基本URL rr.Repo从代理获取模块。
		return newProxyRepo(rr.Repo, path)
	}

	code, err := lookupCodeRepo(rr)
	if err != nil {
		return nil, err
	}
	return newCodeRepo(code, rr.Root, path)
}

func lookupCodeRepo(rr *vcs.RepoRoot) (codehost.Repo, error) {
	code, err := codehost.NewRepo(rr.VCS.Cmd, rr.Repo)
	if err != nil {
		if _, ok := err.(*codehost.VCSError); ok {
			return nil, err
		}
		return nil, fmt.Errorf("lookup %s: %v", rr.Root, err)
	}
	return code, nil
}

// loggingRepo是对基础回购的包装
// 在每次调用的开始和结束时打印日志消息。
// 它可以在调试时插入。
type loggingRepo struct {
	r Repo
}

func newLoggingRepo(r Repo) *loggingRepo {
	return &loggingRepo{r}
}

// logCall使用格式和参数打印日志消息，然后
// 还返回将再次打印相同消息的函数，
// 随着时间的流逝。
// 典型用法是：
// None
// 延迟日志调用（“你好%s”，arg）（）
// None
// 注意最后一个（）。
func logCall(format string, args ...interface{}) func() {
	start := time.Now()
	fmt.Fprintf(os.Stderr, "+++ %s\n", fmt.Sprintf(format, args...))
	return func() {
		fmt.Fprintf(os.Stderr, "%.3fs %s\n", time.Since(start).Seconds(), fmt.Sprintf(format, args...))
	}
}

func (l *loggingRepo) ModulePath() string {
	return l.r.ModulePath()
}

func (l *loggingRepo) Versions(prefix string) (tags []string, err error) {
	defer logCall("Repo[%s]: Versions(%q)", l.r.ModulePath(), prefix)()
	return l.r.Versions(prefix)
}

func (l *loggingRepo) Stat(rev string) (*RevInfo, error) {
	defer logCall("Repo[%s]: Stat(%q)", l.r.ModulePath(), rev)()
	return l.r.Stat(rev)
}

func (l *loggingRepo) Latest() (*RevInfo, error) {
	defer logCall("Repo[%s]: Latest()", l.r.ModulePath())()
	return l.r.Latest()
}

func (l *loggingRepo) GoMod(version string) ([]byte, error) {
	defer logCall("Repo[%s]: GoMod(%q)", l.r.ModulePath(), version)()
	return l.r.GoMod(version)
}

func (l *loggingRepo) Zip(dst io.Writer, version string) error {
	dstName := "_"
	if dst, ok := dst.(interface{ Name() string }); ok {
		dstName = strconv.Quote(dst.Name())
	}
	defer logCall("Repo[%s]: Zip(%s, %q)", l.r.ModulePath(), dstName, version)()
	return l.r.Zip(dst, version)
}

// errRepo是一种对所有操作返回相同错误的Repo。
// None
// 它与缓存结合使用非常有用，因为缓存命中不会尝试
// 禁止的操作。
type errRepo struct {
	modulePath string
	err        error
}

func (r errRepo) ModulePath() string { return r.modulePath }

func (r errRepo) Versions(prefix string) (tags []string, err error) { return nil, r.err }
func (r errRepo) Stat(rev string) (*RevInfo, error)                 { return nil, r.err }
func (r errRepo) Latest() (*RevInfo, error)                         { return nil, r.err }
func (r errRepo) GoMod(version string) ([]byte, error)              { return nil, r.err }
func (r errRepo) Zip(dst io.Writer, version string) error           { return r.err }

// notExistError类似于fs.ErrNotExist，但带有自定义消息
type notExistError struct {
	err error
}

func notExistErrorf(format string, args ...interface{}) error {
	return notExistError{fmt.Errorf(format, args...)}
}

func (e notExistError) Error() string {
	return e.err.Error()
}

func (notExistError) Is(target error) bool {
	return target == fs.ErrNotExist
}

func (e notExistError) Unwrap() error {
	return e.err
}
